﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace GameCamera
{
    /// <summary>
    /// Simple camera class allowing to position camera and specify target point in the game world.
    /// Camera is movable in 6-axis directions, rotates around target point, rolls and zooms.
    /// View apd projection matrices are available from theri respective getters and return
    /// matrices needen for effect.View and effect.Projection in game draw method. 
    /// 
    /// Written by WM, version 0.8
    /// </summary>
    class Camera
    {
        #region Fields
        public Matrix viewMatrix { get; internal set; }
        public Matrix projectionMatrix { get; internal set; }
        protected Matrix _cameraPositionMatrix;                 /* DO NOT USE directly to change camera properties */

        protected Vector3 _lookAt;
        protected float _distance;
        protected float _roll;
        protected float _yaw;
        protected float _pitch;
        protected float _aspectRatio;
        protected float _scale;
        protected float _fov;
        protected float _nPlane;
        protected float _fPlane;

        /* reset values */
        protected float _Rdistance;
        protected float _Rroll;
        protected float _Ryaw;
        protected float _Rpitch;
        #endregion

        #region Constructors
        /// <summary>
        /// Creates camera which is looking at target coordinates.
        /// Yaw, roll and pitch specifies deviation from axes where target point behaves as origin.
        /// </summary>
        /// <param name="lookAt">Position of target.</param>
        /// <param name="distance">Distance from target.</param>
        /// <param name="yaw">Camera yaw in radians.</param>
        /// <param name="pitch">Camera pitch in radians.</param>
        /// <param name="roll">Camera roll in radians.</param>
        /// <param name="aspectRatio">Aspect ratio of camera.</param>
        /// <param name="FOVRad">Field of vision of camera.</param>
        /// <param name="nPlane">Near plane distance.</param>
        /// <param name="fPlane">Far plane distance.</param>
        public Camera(Vector3 lookAt, float distance, float yaw, float pitch, float roll, float aspectRatio, float scale, float FOVRad, float nPlane, float fPlane)
        {
            this._roll = roll;
            this._yaw = yaw;
            this._pitch = pitch;
            this._distance = distance;
            this._lookAt = lookAt;
            this._aspectRatio = aspectRatio;
            this._scale = scale;
            this._fov = FOVRad;
            this._nPlane = nPlane;
            this._fPlane = fPlane;

            this._Rroll = roll;
            this._Ryaw = yaw;
            this._Rpitch = pitch;
            this._Rdistance = distance;

            updateMatrices();
        }
        #endregion

        #region Getters and setters
        public Vector3 lookAt { get { return _lookAt; } set { _lookAt = value; updateMatrices(); } }
        public float distance { get { return _distance; } set { _distance = value; updateMatrices(); } }
        public float roll { get { return _roll; } set { _roll = value; updateMatrices(); } }
        public float yaw { get { return _yaw; } set { _yaw = value; updateMatrices(); } }
        public float pitch { get { return _pitch; } set { _pitch = value; updateMatrices(); } }
        public float aspectRatio { get { return _aspectRatio; } set { _aspectRatio = value; updateMatrices(); } }
        public float fov { get { return _fov; } set { _fov = value; updateMatrices(); } }
        public float nPlane { get { return _nPlane; } set { _nPlane = value; updateMatrices(); } }
        public float fPlane { get { return _fPlane; } set { _fPlane = value; updateMatrices(); } }
        public float Scale { get { return _scale; } set { _scale = value; } }
        #endregion

        #region Public methods
        /// <summary>
        /// Rotates camera around target point.
        /// </summary>
        /// <param name="offsetX">Radians to rotate on horizontal plane right.</param>
        /// <param name="offsetY">Radians to rotate on vertical plane up.</param>
        public void rotateAroundlookAt(float offsetX, float offsetY)
        {
            _yaw -= offsetX;
            _pitch -= offsetY;

            updateMatrices();
        }

        /// <summary>
        /// Moves camera in desired direction.
        /// </summary>
        /// <param name="offsetX">Offset on X camera axis.</param>
        /// <param name="offsetY">Offset on Y camera axis.</param>
        /// <param name="offsetZ">Offset on Z camera axis.</param>
        public void moveCamera(float offsetX, float offsetY, float offsetZ)
        {
            Vector3 xAxis = _cameraPositionMatrix.Right;
            Vector3 yAxis = _cameraPositionMatrix.Up;
            Vector3 zAxis = _cameraPositionMatrix.Forward;
            /* probably not needed
            xAxis.Normalize();
            yAxis.Normalize();
            zAxis.Normalize();
            */
            _lookAt -= offsetX * xAxis;
            _lookAt -= offsetY * yAxis;
            _lookAt -= offsetZ * zAxis;

            updateMatrices();
        }

        /// <summary>
        /// Rolls camera clockwise.
        /// </summary>
        /// <param name="offset">Roll angle in radians.</param>
        public void rollCamera(float offset)
        {
            _roll -= offset;

            updateMatrices();
        }

        /// <summary>
        /// Changes distance from target point.
        /// </summary>
        /// <param name="distance">Distance difference.</param>
        public void zoom(float distance)
        {
            _distance -= distance;

            updateMatrices();
        }

        /// <summary>
        /// Reset camera to start values.
        /// </summary>
        public void resetYawPitchRollDistance()
        {
            _yaw = _Ryaw;
            _roll = _Rroll;
            _pitch = _Rpitch;
            _distance = _Rdistance;

            updateMatrices();
        }
        #endregion

        #region Protected methods
        /// <summary>
        /// Recreate matrices after value change. Must be called explicitly to ensure correct matrices.
        /// </summary>
        protected void updateMatrices()
        {
            // create roll
            Matrix rollMatrix = Matrix.CreateRotationZ(roll);

            // Get the translation offset for an arcball system
            Matrix offsetMatrix = Matrix.CreateTranslation(0.0f, 0.0f, _distance);
            offsetMatrix = Matrix.Multiply(offsetMatrix, Matrix.CreateFromYawPitchRoll(_yaw, _pitch, 0));
            offsetMatrix = Matrix.Multiply(offsetMatrix, Matrix.CreateTranslation(_lookAt));

            _cameraPositionMatrix = Matrix.Multiply(rollMatrix, offsetMatrix);
            // Combine the transforms and invert
            viewMatrix = Matrix.Invert(_cameraPositionMatrix);

            // Create projection Matrix
            projectionMatrix = Matrix.CreatePerspectiveFieldOfView(_fov, _aspectRatio, _nPlane, _fPlane);
        }
        #endregion
    }
}
